
///
/// This is the implementation of the Handler class

///
#include "fcntl.h"
#include "stdlib.h"
#include "stdio.h"
#include "netinet/in.h"
#include "Handler.h"
#include <cstring>
#include <iostream>
#include <cassert>
#include <string>
#include "Server.h"
#include "Client.h"
using namespace std;


Handler::Handler(int fd, std::string& fileName, 
			struct sockaddr_storage clientAddr, socklen_t clientAddrSize) : 
			mSockFD(fd), mFileName(fileName), mClientAddr(clientAddr), 
			mClientAddrSize(clientAddrSize)
{
#if 0	// lets try sending File Not Found error
	
	uint16_t host, nw;
	
	char* head = mMsgBuf;

	host = 5;	// error opcode
	nw = htons(host);
	memcpy(head, (char*)&nw, 2);
	head += 2;

	host = 1;	// File Not Found error code
	nw = htons(host);
	memcpy(head, (char*)&nw, 2);
	head += 2;

	char msg[] = "Testing communication!";
	memcpy(head, msg, strlen(msg));
	head += strlen(msg);

	char endByte = 0;
	memcpy(head, &endByte, 1);
	++head;
	
	int numBytesSent;
	if((numBytesSent = sendto(mSockFD, mMsgBuf, head - mMsgBuf, 0,
						(struct sockaddr*)&mClientAddr, mClientAddrSize)) == -1)
	{
		perror("sendto() error: ");
		exit(1);
	}
	std::cout << "Sent!!!" << std::endl;

#else // lets try sending one data packet and wait for ACK
	//mSockFD = fd;
	
#endif

}

int Handler::initial(std::string fileName,sockaddr_storage clientAddr, socklen_t clientAddrSize)
{
	memcpy(&mClientAddr,&clientAddr, sizeof(mClientAddr));
	memcpy(&mFileName,&fileName, sizeof(fileName));	
	memcpy(&mClientAddrSize, &clientAddrSize, sizeof(mClientAddrSize));
	
	uint16_t host, nw;

        char* head = mMsgBuf;

        host = 3;       // data opcode
        nw = htons(host);
        memcpy(head, (char*)&nw, 2);
        head += 2;

        host = 1;       // block# 1
        nw = htons(host);
        memcpy(head, (char*)&nw, 2);
        head += 2;

        //read file according to the FILENAME
        char msg[TFTP_MAX_SIZE];
        //modify filename input
        //ifstream goalfile("fileName");
        const char *name = fileName.c_str();
	std::cout<<"filename is "<<name<<std::endl;
        fin = open(name, O_RDONLY);
        if(fin == -1)
        {
                cout<<"Unable to open goalfile!";
                //TODO send error packet

                exit(1);
        }

        read(fin, msg, TFTP_MAX_SIZE);

        std::cout<<"the message length is "<<strlen(msg)<< std::endl;

        if (strlen(msg)>=512)
        {
        //      cout<<"testing"<<endl;
                previous_block = 1;
                next_block = 2;
        //      cout<<"testing end"<<endl;
        }
        else
        {
                //TODO do we need close this socket???
                previous_block = 0;
                next_block = 0;
                cout<<"next_block is "<<next_block<<endl;
                cout<<"previous_block is "<<previous_block<<endl;
        }

        //char msg[] = "Testing communication!";
        memcpy(head, msg, strlen(msg));
        head += strlen(msg);

        int numBytesSent;
        if((numBytesSent = sendto(mSockFD, mMsgBuf, head - mMsgBuf, 0,
                                                (struct sockaddr*)&clientAddr, clientAddrSize)) == -1)
        {
		std::cout<<"fd is "<<mSockFD<<std::endl;
		
                perror("sendto() error: ");
                exit(1);
        }
        std::cout << "Sent!!!" << std::endl;
        std::cout<< "previous_block is "<< previous_block<<std::endl;
        std::cout<< "next_block is "<<next_block<<std::endl;


}

Handler::Handler(const Handler& rhs)
{
	mSockFD = rhs.mSockFD;
	//mMsgBufPtr = rhs.mMsgBufPtr;
	//mMsgLength = rhs.mMsgLength;
	memcpy(mMsgBuf, rhs.mMsgBuf, MSG_BUF_SIZE);
}

Handler& Handler::operator=(const Handler& copy)
{
	mSockFD = copy.mSockFD;
	//mMsgBufPtr = copy.mMsgBufPtr;
	//mMsgLength = copy.mMsgLength;
	memcpy(mMsgBuf, copy.mMsgBuf, MSG_BUF_SIZE);
	return *this;
}

int Handler::HandleMessage(const char *packet, const unsigned int packetLength)
{
	/*std::cout << "Message of length " << packetLength << " received from client: " << std::endl;

	for(unsigned int i = 0; i < packetLength; ++i)
	{
		//std::cout << std::hex << packet[i] << " ";
		printf("%x ", packet[i]);
	}
	std::cout << std::endl;*/


	uint16_t host, nw;
	const char* head = packet;
	char* send_head = mMsgBuf;

	std::cout<<"current fd is "<<mSockFD<<std::endl;
	std::cout<<"fin is "<<fin<<std::endl;
	memcpy((char*)&nw, head, 2);
	host = ntohs(nw);
	head += 2;
	cout<<"receive opcode is "<<host<<endl;
	
	if(host == 4)
	{
		std::cout << "ACK received for block number: ";

		memcpy((char*)&nw, head, 2);
		host = ntohs(nw);
		head += 2;

		std::cout << host << std::endl;
		//check whether the block number is the same to the number on statemachine
		//if same, send new data and modify the state machine
		std::cout <<"previous_block is "<< previous_block <<std::endl;
		std::cout <<"next_block is "<< next_block <<std::endl;
		if ((host == previous_block)&&(next_block==previous_block+1))
		{
			char msg[TFTP_MAX_SIZE];
		        host = 3;       // data opcode
		        nw = htons(host);
		        memcpy(send_head, (char*)&nw, 2);
		        send_head += 2;
	
		        host = previous_block + 1;       // block# previous_block + 1
		        nw = htons(host);
		        memcpy(send_head, (char*)&nw, 2);
		        send_head += 2;
			lseek(fin,0,512*previous_block);	
			read(fin, msg, TFTP_MAX_SIZE);
			memcpy(send_head, msg, strlen(msg));
			send_head += strlen(msg);
			previous_block += 1;
			
			if(strlen(msg)>=512)
			{
				cout<<"testing"<<endl;
				cout<<"message length:"<<strlen(msg)<<std::endl;
				next_block += 1;
				cout<<"testing end"<<endl;
			}
			int numBytesSent;
			if((numBytesSent = sendto(mSockFD, mMsgBuf, send_head - mMsgBuf, 0,
						(struct sockaddr*)&mClientAddr, mClientAddrSize)) == -1)
			{
				perror("sendto error!");
				exit(1);
			}
			cout<<"block "<<previous_block<<" sent!!!"<<endl;
		}
		//TODO if block number received is smaller, disregard?
		//TODO if block number received is bigger, send new data and modify the state machine
	}

	if(host == 5)
	{
		//TODO Process ErrorMessage	
	}
		




#if 0	//for UDP, sent = received, no buffering needed
	//
	// packetLength is the incoming packet (might consist of multiple messages)
	// mMsgLength is the known length of the current message that we're waiting to fully receive
	// mMsgBufPtr is the index in the buf of the next empty location (indicating the length of
	// received already
	//
	unsigned int bytesToEat = packetLength;
	const char * packetEatingPoint = packet;
	while(bytesToEat != 0)
	{
		if(mMsgBufPtr < HEADER_SIZE) // we don't have the header yet
		{
			unsigned int bytesNeeded = std::min(bytesToEat, HEADER_SIZE - mMsgBufPtr); //pick as much as you need from whats given

			memcpy(mMsgBuf + mMsgBufPtr, packetEatingPoint, bytesNeeded);
			bytesToEat -= bytesNeeded;
			packetEatingPoint += bytesNeeded;
			mMsgBufPtr += bytesNeeded;

			if(mMsgBufPtr >= HEADER_SIZE) // we got what we wanted(actually it should be exactly equal)
			{
				assert(mMsgBufPtr == HEADER_SIZE); //TODO: remove later and change the if() to exactly equal
	
				// ACT ON THE HEADER HERE

				/*uint32_t temp;
				memcpy((char*)&temp, mMsgBuf, HEADER_SIZE);
				uint32_t currHeader = ntohl(temp);
				memcpy((char*)&mCurrHeader, (char*)&currHeader, HEADER_SIZE);*/
				uint16_t nw, host;
				memcpy((char*)&nw, mMsgBuf, 2);
				host = ntohs(nw);
				memcpy((char*)&mCurrHeader, (char*)&host, 2);
				memcpy((char*)&nw, mMsgBuf + 2, 2);
				host = ntohs(nw);
				memcpy(((char*)(&mCurrHeader))+2, (char*)&host, 2);

				//std::cout << "Header: " << mCurrHeader.version << " " << mCurrHeader.type << " " << mCurrHeader.length << std::endl; 
				
				mMsgLength = mCurrHeader.length; //TODO: probably won't need mMsgLength
			}
		}
		else // we have the header already, accumulate the body
		{
			unsigned int bytesNeeded = std::min(bytesToEat, mMsgLength - mMsgBufPtr); //pick as much as you need from whats given

			memcpy(mMsgBuf + mMsgBufPtr, packetEatingPoint, bytesNeeded);
			bytesToEat -= bytesNeeded;
			packetEatingPoint += bytesNeeded;
			mMsgBufPtr += bytesNeeded;

			if(mMsgBufPtr >= mMsgLength) // we got what we wanted(actually it should be exactly equal)
			{
				assert(mMsgBufPtr == mMsgLength); //TODO: remove later and change the if() to exactly equal

				// ACT ON THE MESSAGE HERE	
				ActOnMessage();

				mMsgBufPtr = 0; // this will make it read the next message next iteration
			}
		}
	}
#endif
	return 0;
}
#if 0
int Handler::ActOnMessage()
{
	// SERVER HANDLES:
	// JOIN: need to check joinability based on client count and username, update client count and username map
	//       send ONLINE to other clients with username, send ACK with other client's username to the joinee
	// LEAVE: need to decrease client count and update username map
	//		  send OFFLINE to other clients with username, update fd set
	// SEND: send FWD to other clients

	// CLIENT HANDLES:
	// ACK: print all usernames to screen with online message
	// NACK: print reason, close socket and return
	// ONLINE: print username with message
	// OFFLINE: print username with message
	// FWD: print message with username

	//static uint32_t temp, currAttr;
	static uint16_t nw, host;
	static StructAttribute tempAttrib;

	if(mCurrHeader.type == HEADER_TYPE_LEAVE) //LEAVE has no attributes
	{
		Server::GetInstance()->HandleLeaveCommand(mSockFD);
	}
	else if(mCurrHeader.type == HEADER_TYPE_FWD) //FWD has multiple attributes
	{

		int bytesToEat =  mMsgLength - HEADER_SIZE;
		const char* byteEatingPoint = mMsgBuf + HEADER_SIZE;

		static char username[17];
		static char message[513];

		while(bytesToEat != 0)
		{
			/*memcpy((char*)&temp, byteEatingPoint, ATTR_SIZE);
			currAttr = ntohl(temp);
			memcpy((char*)&tempAttrib, (char*)&currAttr, ATTR_SIZE);*/
			memcpy((char*)&nw, byteEatingPoint, 2);
			host = ntohs(nw);
			memcpy((char*)&tempAttrib, (char*)&host, 2);
			memcpy((char*)&nw, byteEatingPoint + 2, 2);
			host = ntohs(nw);
			memcpy(((char*)(&tempAttrib))+2, (char*)&host, 2);

			byteEatingPoint += ATTR_SIZE;
			bytesToEat -= ATTR_SIZE;

			unsigned int length = tempAttrib.length - ATTR_SIZE; 
			if(tempAttrib.type == ATTR_TYPE_MESSAGE)
			{
				memcpy(&message[0], byteEatingPoint, length);
				message[length] = 0;
			}
			else if(tempAttrib.type == ATTR_TYPE_USERNAME)
			{
				memcpy(&username[0], byteEatingPoint, length);
				username[length] = 0;
			}
			else
			{
				assert(false);
			}
			byteEatingPoint += length;
			bytesToEat -= length;
		}
		std::cout << username << ": " << message << std::endl;

	}
	else if(mCurrHeader.type == HEADER_TYPE_ACK) // ACK has multiple attributes
	{
		int bytesToEat =  mMsgLength - HEADER_SIZE;
		const char* byteEatingPoint = mMsgBuf + HEADER_SIZE;

		unsigned short clientCount;
		char username[17];

		while(bytesToEat != 0)
		{
			/*memcpy((char*)&temp, byteEatingPoint, ATTR_SIZE);
			currAttr = ntohl(temp);
			memcpy((char*)&tempAttrib, (char*)&currAttr, ATTR_SIZE);*/
			memcpy((char*)&nw, byteEatingPoint, 2);
			host = ntohs(nw);
			memcpy((char*)&tempAttrib, (char*)&host, 2);
			memcpy((char*)&nw, byteEatingPoint + 2, 2);
			host = ntohs(nw);
			memcpy(((char*)(&tempAttrib))+2, (char*)&host, 2);

			byteEatingPoint += ATTR_SIZE;
			bytesToEat -= ATTR_SIZE;

			unsigned int length = tempAttrib.length - ATTR_SIZE; 
			if(tempAttrib.type == ATTR_TYPE_CLIENT_COUNT)
			{
				//memcpy((char*)(&clientCount), byteEatingPoint, length); //essentially 2 bytes

				uint16_t temp16, clientCount16;
				memcpy((char*)&temp16, byteEatingPoint, length);
				clientCount16 = ntohs(temp16);
				memcpy((char*)&clientCount, (char*)&clientCount16, length);

				std::cout << "\n\nThis chat room has " << clientCount << " other users:" << std::endl;
			}
			else if(tempAttrib.type == ATTR_TYPE_USERNAME)
			{
				memcpy(&username[0], byteEatingPoint, length);
				username[length] = 0;
				std::cout << username << ", ";
			}
			else
			{
				assert(false);
			}
			byteEatingPoint += length;
			bytesToEat -= length;
		}
		std::cout << "\n\n";
	}
	else //single attribute messages
	{
		/*memcpy((char*)&temp, mMsgBuf + HEADER_SIZE, ATTR_SIZE);
		currAttr = ntohl(temp);
		memcpy((char*)&tempAttrib, (char*)&currAttr, ATTR_SIZE);*/
		memcpy((char*)&nw, mMsgBuf + HEADER_SIZE, 2);
		host = ntohs(nw);
		memcpy((char*)&tempAttrib, (char*)&host, 2);
		memcpy((char*)&nw, mMsgBuf + HEADER_SIZE + 2, 2);
		host = ntohs(nw);
		memcpy(((char*)(&tempAttrib))+2, (char*)&host, 2);

	
		std::string textPayload(mMsgBuf + HEADER_SIZE + ATTR_SIZE, tempAttrib.length - ATTR_SIZE);

		switch(mCurrHeader.type)
		{
			case HEADER_TYPE_JOIN:
				if(tempAttrib.type == ATTR_TYPE_USERNAME)
				{
					std::cout << "Received JOIN from: " << textPayload.c_str() << std::endl;
					Server::GetInstance()->HandleJoinCommand(mSockFD, textPayload);
				}
				else
				{
					std::cout << "Invalid Attribute Type for JOIN: " << tempAttrib.type << std::endl;
				}
			break;

			case HEADER_TYPE_NAK:
				if(tempAttrib.type == ATTR_TYPE_REASON)
				{
					std::cout << "Received NAK from server with reason: " << textPayload.c_str() << std::endl;
					Client::GetInstance()->HandleNakCommand();
				}
				else
				{
					std::cout << "Invalid Attribute Type for NAK: " << tempAttrib.type << std::endl;
				}

			break;

			case HEADER_TYPE_OFFLINE:
				if(tempAttrib.type == ATTR_TYPE_USERNAME)
				{
					std::cout << textPayload.c_str() << " is offline!" << std::endl;
				}
				else
				{
					std::cout << "Invalid Attribute Type for OFFLINE: " << tempAttrib.type << std::endl;
				}

			break;

			case HEADER_TYPE_ONLINE:
				if(tempAttrib.type == ATTR_TYPE_USERNAME)
				{
					std::cout << textPayload.c_str() << " is online!" << std::endl;
				}
				else
				{
					std::cout << "Invalid Attribute Type for ONLINE: " << tempAttrib.type << std::endl;
				}
			break;

			case HEADER_TYPE_SEND:
				if(tempAttrib.type == ATTR_TYPE_MESSAGE)
				{
					std::cout << "Received SEND with message: " << textPayload.c_str() << std::endl;
					Server::GetInstance()->HandleSendCommand(mSockFD, textPayload);
				}
				else
				{
					std::cout << "Invalid Attribute Type for SEND: " << tempAttrib.type << std::endl;
				}

			break;

			default:
				std::cout << "Invalid Header Type: " << mCurrHeader.type << std::endl;
			break;
		}
	}
	return 0;
}
#endif
